Assigned on: March 23, 2001 Due on: April 11, 2001
Execute the C programs given in the following
problems. Observe and Interpret the results. You will learn about UNIX files by performing the
suggested experiments.
Some of the UNIX calls used in the following
problems are: open(), close(), read(),
write(), fopen(), fread(), fwrite(), lockf(),
fflush(), fork() and execl().
Use the on-line help or a UNIX book for further information on UNIX calls.
Submission: The answer for each problem
should be provided in the following format:
(a) a report of your observations, (b) an
explanation of observed results. In addition to answers for individual
questions write a cohesive summary of what you have learnt through the various experiments done as a part of
this homework. The summary must be typed on a separate sheet of paper. Staple
the pages together in the following order: (I) Cover sheet, (II) Summary
sheet(s), (III) the answers for individual problems. The homework must be
submitted in the required format, at the beginning of the class period on due
date. The late homework will be accepted only
during the next class period
after the due date, with 10% late penalty.
1. Run the following program
and observe the output. The objective is to understand how the open files are shared by the child and
the parent process.
Note: There is a file descriptor
table for each process. This table is duplicated for the child process. As
a result, all open files in the parent
process when the fork() is called,
are also open in the child process. When a file is opened entries are made in
the file descriptor table and the
system table, which is global and not duplicated. Both these entries are
linked. The system table entry contains the file pointer, the access mode etc.
The open files, associated file
pointers and the access modes are shared by the parent and the child processes.
#include
<stdio.h>
#include
<fcntl.h>
main(
)
{
int fp;
char buff[20];
int pid;
fp = open("sample",
O_RDONLY);
pid = fork( );
if (pid==0)
{
printf("child
begins %d\n" , getpid( ));
read(fp,buff,10);
buff[10]
= ‘\0’;
printf("child
read: ");
puts(buff);
printf("child
exits\n");
}
else
{
printf("parent
begins %d\n" , getpid( ));
read(fp,buff,10);
buff[10]
= ‘\0’;
printf("parent
read: ");
puts(buff);
printf("parent
exits\n");
}
}
You may want to try other experiments, for example, create the child first and open the same file individually within the parent and the child.
2. Run the following program
and observe the file pointer position
(printed by the parent process). Note that the child process reads 10
characters from the file before the control goes to the parent process. Is the
file pointer position 11? What happened?
Note: The three parameters for lseek( ) are: the file handle, the
number of characters to move, and from where to move.
#include
<stdio.h>
#include
<fcntl.h>
main(
)
{
int fp;
char buff[20];
int pid;
fp =
open("sample",O_RDONLY);
pid = fork( );
if (pid==0)
{
printf("child:
file handle is %d\n", lseek(fp,01,1));
read(fp,buff,10);
buff[10]
= ‘\0’;
printf("child:
file handle is now %d\n",
lseek(fp,01,1)) ;
}
else
{
wait(0);
printf("parent:
file handle is %d\n", lseek(fp,01,1));
}
}
3. The following program uses
the high-level operations fopen(),
fread() instead of the low-level operations open() and read() . Run
the program and explain the results.
Note: The high-level call fread() is buffered , i.e., by default,
it reads a block of data into a system buffer. The difference due buffering
will be reflected by the two
observations. The block size is
depends on the system.
#include
<stdio.h>
#include
<fcntl.h>
main(
)
{
FILE * fp;
char buff[20];
int pid;
fp = fopen("sample",
"r" );
pid = fork( );
if (pid==0)
{
printf("child:
initial file pointer is %d\n", ftell(fp));
fread(buff,
sizeof(buff), 1,fp);
buff[10]
= ‘\0’;
printf("child
read: %s\n", buff);
printf("child:
file pointer is %d\n", ftell(fp));
printf("child
exits\n");
}
else
{
wait(0);
printf("parent:
initial file pointer is %d\n", ftell(fp));
fread(buff,
sizeof(buff), 1,fp);
buff[10]
= ‘\0’;
printf("parent
read: %s\n", buff);
printf("parent:
file pointer is %d\n", ftell(fp));
printf("parent
exits\n");
}
}
4. The UNIX uses two file
pointers: the logical file pointer and the physical file pointers whose values
can be obtained by ftell() and lseek() calls respectively. Design an experiment to
observe how these file pointers operate. Suggestion:
Write a program where the child process reads 10 characters, the parent
process reads 10 characters, and finally the child process reads 10 characters
again. Use file with a succession of n A’s, n B’s, n C’s and n D’s where n is the size of the block used for
high-level operations. You may choose to design the experiment differently, if
you wish.
5. Run the following program.
Observe the contents of the file mystery
using the cat command. How many times
the message is written to the file? Why?
#include <stdio.h>
#include <fcntl.h>
main( )
{
char
*p = "The Man who mistook his wife for a hat by Oliver Sacks";
FILE
*fp;
fp
= fopen("mystery ",
"w");
fwrite(p,8,1,fp);
fork();
}
6. Run the following program.
Do you observe any output on the terminal?
Repeat the experiment to observe the results of following changes (to be
made one at a time) in the program: (a) include \n in the print statement, (b)
insert fflush(stdout) statement after the print statement.
Some information about UNIX: The terminal output is buffered. Unless the
buffer is full, physically flushed by us, or the process terminates the contents
of the buffer will not be displayed. When exec() is executed, the process is replaced in memory by the one
that has been called. The buffers which contain data from the previous process
are emptied.
NOTE: You will also need to create ex2.c. Just create a simple ex2.c
with one print statement:
printf("I will do all my homework but
...");
#include <stdio.h>
main( )
{
printf("Will
you print this message? ");
execl("ex2",
"ex2", (char *)0);
}
7. Run in the background two processes that write to the same file.
Observe that the writes to the file are jumbled up. Fix the problem by using lockf() function.
8. Convert the supplied program
to use threads.
/* Include Files */
#include <stdio.h>
/* External References */
extern int hello( void );
extern int world( void );
main( int argc, char *argv[] ) {
hello();
world();
printf(
"\n" );
return( 0
);
}
/* hello - print the "hello" part. */
int
hello( void ) {
printf(
"hello " );
return( 0
);
}
/* world - print the "world" part. */
int
world( void ) {
printf(
"world" );
return( 0
);
}
The following restrictions apply:
1) One
thread will print "hello ", one thread will print "world",
the
main function will print the trailing "\n".
2) The
"world" thread will be created before the "hello" thread.
3) Both
the "hello" and "world" thread must run concurrently.
Hints & Tips:
1) You must use a
synchronization primitive to synchronize execution of the two threads; hello"
and "world".
2) You must use a
synchronization method to assure that the main thread does not execute
until
after the "world" thread.
3) You should not assume that the first created thread will be executed first, the second created thread will be executed second, and so on, i.e., the order of execution of the created threads may depend on the scheduling algorithm of the system, which may not be FIFO.